package com.spring;

import java.beans.Introspector;
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class ZhouyuApplicationContext {

    private Class configClass;

    /**
     * 在扫描的时候，会生成  beanDefinition ，并放入这个map中
     * key为beanName,value-beanDefinition
     */
    private Map<String,Object> beanDefinitionMap=new HashMap();

    // 存储单例bean的
    private Map<String,Object> singletonObjects=new HashMap();

    private List<BeanPostProcessor> beanPostProcessorList = new ArrayList();


    public ZhouyuApplicationContext(Class configClass) {
        // 在当前类中，保存配置类
        this.configClass=configClass;

        // 扫描,创建beanDefination,并将数据放入 beanDefinitionMap中
        sacn(configClass);

        // 扫描完成之后，需要创建单例bean对象，就是遍历 beanDefinitionMap
        // 对beanDefinitionMap其中的每一key，创建bean对象
        for (Map.Entry<String, Object> entry : beanDefinitionMap.entrySet()) {
            String beanName = entry.getKey();
           BeanDefinition beanDefinition= (BeanDefinition) entry.getValue();
           // 如果是单例对象，那么创建单例对象，并放入单例池中
            // key - branName,value-bean对象
            if("singleton".equalsIgnoreCase(beanDefinition.getScope())){
                Object bean = createBean(beanName, beanDefinition);
                singletonObjects.put(beanName,bean);
            }
        }
    }


    /**
     * 创建一个对象
     * @param beanName
     * @param beanDefinition
     * @return
     */
    private Object createBean(String beanName,BeanDefinition beanDefinition)  {
        // 获取到这个类的类对象
        Class clazz = beanDefinition.getType();
        Object instance=null;
        try {
            // 这里使用的是无参构造方法
            // 如果我们想要使用有参的构造方法的话，那么有参的构造方法中的数据怎么来？
            instance  = clazz.getConstructor().newInstance();

            // 依赖注入
            // 上面只是创建了一个对象，此时还没有给当前类中 那些标注了@Autowire 注解的属性进行赋值，其他属性不在这个是否赋值，所以接下来
            // 我们需要获取当前类中的所有属性，然后判断取出这些标注了@Autowire注解的属性
            for (Field field : clazz.getDeclaredFields()) {
                if(field.isAnnotationPresent(Autowired.class)){
                    field.setAccessible(true);
//                    field.set(instance,要付什么值);
                    // 获取当前属性的名字 field.getName()
                    // 然后我们根据这个属性的名字，去获取
                    field.set(instance,getBean(field.getName()));
                }
            }

            // 回调
            if(instance instanceof BeanNameAware){
                ((BeanNameAware) instance).setBeanName(beanName);
            }

            /**
             * BeanPostprocessor 初始化前
             * 只要是创建bean，那么 beanPostProcessor 都会执行
             */
            for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
                instance= beanPostProcessor.postProcessBeforeInitialization(instance,beanName);
            }

            /**
             * 初始化的实现
             * 判断当前的这个实例对象，是否实现了 InitializingBean 接口
             */
            if(instance instanceof  InitializingBean){
                ((InitializingBean)instance).afterPropertiesSet();
            }

            /**
             * 初始化后
             *
             * AOP在这里实现【*********************】
             */
            for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
                instance= beanPostProcessor.postProcessAfterInitialization(instance,beanName);
            }



        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        return instance;
    }



    /**
     * 模拟 spring getBean方法
     * @param beanName
     * @return
     */
    public Object getBean(String beanName){
        // beanDefinitionMap 这个map中包含了 单例bean 也包含了 原型bean,
        if(!beanDefinitionMap.containsKey(beanName)){
            throw new NullPointerException();
        }
        BeanDefinition beanDefinition = (BeanDefinition) beanDefinitionMap.get(beanName);

        // 如果是单例bean的话，在扫描路径，获取标注了 @component注解的类时，这个 时候还会获取@scope注解，
        // 去判断是单例bean还是原型bean,如果是单例bean，那么这个时候就会创建这个bean对应的对象，
        // 如果是原型的话，不做操作，在getBean调用的时候，才去创建对象
        if("singleton".equalsIgnoreCase(beanDefinition.getScope())){
            Object singletonBean = singletonObjects.get(beanName);
            /**
             *
             */
            if(singletonBean == null){
                singletonBean = createBean(beanName, beanDefinition);
                singletonObjects.put(beanName,singletonBean);
            }
            return singletonBean;
        }else {
            // 原型
            Object bean = createBean(beanName, beanDefinition);
            return bean;
        }
    }


    /**
     * 扫描
     * @param configClass
     */
    private void sacn(Class configClass) {
        // 判断传入的配置类是否存在@ComponentScan注解（扫描路径的注解）
        if(configClass.isAnnotationPresent(ComponentScan.class)){
            // 如果传入的配置类上存在注解，那么获取注解中的扫描路径
            ComponentScan componentScan = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
            // 从注解中获取配置的扫描路径
            String path = componentScan.value();
            path = path.replace(".","/"); // 相对路径
            System.out.println(path);
            // 获取到扫描路径之后，就需要拿到这个路径下所有的 类，判断这些类上是否标注了 @Component注解，只有标注了这个注解的类，才交给spring创建bean对象
            // 上面我们获取到 扫描路径之后，我们怎么获取到这个路径下的所有类呢？
            // 我们需要使用 appClassLoader 类加载器，取获取 扫描路径下的所有类
            ClassLoader classLoader = ZhouyuApplicationContext.class.getClassLoader();
            URL resource = classLoader.getResource(path); // 获取具体路径
            // file 既可以表示目录，也可以表示文件
            File file = new File(resource.getFile());
            // 判断当前路径是否是一个目录
            if(file.isDirectory()){
                // 遍历 扫描路径path 下的所有文件
                for (File f : file.listFiles()) {
                    String absolutePath = f.getAbsolutePath();
                    System.out.println("1->"+absolutePath);
                    absolutePath = absolutePath.substring(absolutePath.indexOf("com"),absolutePath.indexOf(".class"));
                    absolutePath=absolutePath.replace("\\",".");
                    // 判断 当前类 是否存在@Component注解，通过类加载器，将当前类加载成 .class对象
                    System.out.println("2->"+absolutePath);
                    try {
                        Class<?> clazz = classLoader.loadClass(absolutePath);
                        // 判断当前类是否标注了 component注解,有这个 component注解的话，那么就表示当前类是一个bean，我们就需要封装一个 BeanDefition对象
                        if(clazz.isAnnotationPresent(Component.class)){

                            // 判断当前类是否是 BeanPostProcessor 接口的实现类
                            if (BeanPostProcessor.class.isAssignableFrom(clazz)) {
                                try {
                                    BeanPostProcessor beanPostProcessor = (BeanPostProcessor) clazz.getConstructor().newInstance();
                                    beanPostProcessorList.add(beanPostProcessor);
                                } catch (InstantiationException e) {
                                    e.printStackTrace();
                                } catch (IllegalAccessException e) {
                                    e.printStackTrace();
                                } catch (InvocationTargetException e) {
                                    e.printStackTrace();
                                } catch (NoSuchMethodException e) {
                                    e.printStackTrace();
                                }
                            }

                            Component component = clazz.getAnnotation(Component.class);
                            String beanName = component.value();
                            if("".equals(beanName)){
                                // 表示标注了 @component（没有写）注解时，没有指定值
                                // Introspector.decapitalize(clazz.getSimpleName()); 这个方法会在 @component（没有写）时，使用的时当前类名的首字母小写
                                // 例如我们在 OrderService类上加上注解 @component（没有写）时，这里就会使用 orderService作为beanName
                                beanName = Introspector.decapitalize(clazz.getSimpleName());
                            }


                            BeanDefinition beanDefinition = new BeanDefinition();
                            beanDefinition.setType(clazz); // 标注了 @Component注解的类的类对象
                            // 接下来要考虑什么呢？接下来我们需要考虑这个类上是否是单例的，还是原型的（scope注解）
                            System.out.println("3->"+clazz);
                            if(clazz.isAnnotationPresent(Scope.class)){
                                // 如果有这个scope注解，我们还需要判断scope注解中写的什么内容 @Scope("prototype") ， @Scope("singleton")
                                Scope scopeAnnotation = clazz.getAnnotation(Scope.class);
                                String scopeValue = scopeAnnotation.value();
                                beanDefinition.setScope(scopeValue);
                            }else {
                                // 没有这个 scope注解的话，默认是单例的
                                beanDefinition.setScope("singleton");
                            }
                            beanDefinitionMap.put(beanName,beanDefinition);
                        }
                    }catch (ClassNotFoundException e){
                        System.out.println(e.getMessage());
                    }
                }
            }

        }
    }




}
